package com.mars.module.system.service.impl;


import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.mars.common.base.UserContextInfo;
import com.mars.common.request.sys.*;
import com.mars.common.util.RequestUtils;
import com.mars.common.util.TokenUtils;
import com.mars.common.response.sys.*;
import com.mars.common.response.sys.LoginResponse;
import com.mars.common.constant.Constant;
import com.mars.common.response.PageInfo;
import com.mars.common.util.ip.IpUtils;
import com.mars.framework.async.AsyncFactory;
import com.mars.framework.config.EasyAdminConfig;
import com.mars.framework.context.ContextUserInfoThreadHolder;
import com.mars.framework.redis.RedisCache;
import com.mars.module.admin.entity.SysUserMessageStats;
import com.mars.module.admin.mapper.SysUserMessageStatsMapper;
import com.mars.module.system.entity.*;
import com.mars.framework.exception.ServiceException;
import com.mars.module.system.mapper.SysRoleMapper;
import com.mars.module.system.mapper.SysUserMapper;
import com.mars.module.system.mapper.SysUserPostMapper;
import com.mars.module.system.mapper.SysUserRoleMapper;
import com.mars.common.util.IdUtils;
import com.mars.module.system.service.ISysMenuService;
import com.mars.module.system.service.ISysUserService;
import com.mars.module.tool.entity.SysLoginRecord;
import com.mars.module.tool.mapper.SysLoginRecordMapper;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.AllArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 用户Service
 *
 * @author 源码字节-程序员Mars
 */
@Service
@AllArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class SysUserServiceImpl implements ISysUserService {

    private final SysUserMapper sysUserMapper;

    private final SysRoleMapper sysRoleMapper;

    private final SysUserRoleMapper sysUserRoleMapper;

    private final ISysMenuService sysMenuService;

    private final SysUserPostMapper sysUserPostMapper;

    private final SysUserMessageStatsMapper sysUserMessageStatsMapper;

    private final RedisCache redisCache;

    private final TokenUtils tokenUtils;

    private final SysLoginRecordMapper sysLoginRecordMapper;

    private final EasyAdminConfig easyAdminConfig;

    @Override
    public SysUser selectByUserName(String userName) {
        return sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUserName, userName));
    }

    @Override
    public SysUserDetailResponse get(Long id) {
        //获取用户
        SysUser sysUser = sysUserMapper.selectById(id);
        if (sysUser == null) {
            throw new ServiceException("数据不存在");
        }
        SysUserDetailResponse userVo = new SysUserDetailResponse();
        BeanUtils.copyProperties(sysUser, userVo);

        //获取用户角色
        List<SysRole> roleList = sysRoleMapper.selectByUserId(id);
        if (CollectionUtils.isNotEmpty(roleList)) {
            List<Long> roleIdList = roleList.stream().map(SysRole::getId).collect(Collectors.toList());
            userVo.setRoleId(roleIdList);
        }
        SysUserPost sysPost = sysUserPostMapper.selectOne(Wrappers.lambdaQuery(SysUserPost.class).eq(SysUserPost::getUserId, id));
        if (Objects.nonNull(sysPost)) {
            userVo.setPostId(sysPost.getPostId());
        }
        return userVo;
    }

    @Override
    public PageInfo<SysUserListResponse> pageList(SysUserQueryRequest queryDto) {
        IPage<SysUserListResponse> joinPage = sysUserMapper.selectPageList(queryDto.page(), queryDto);
        List<SysUserListResponse> records = joinPage.getRecords();
        if (CollectionUtils.isNotEmpty(records)) {
            List<Long> userIds = records.stream().map(SysUserListResponse::getId).collect(Collectors.toList());
            List<SysRoleListResponse> sysRoleListResponses = sysRoleMapper.selectUserRoleList(userIds);
            Map<Long, List<SysRoleListResponse>> listMap = sysRoleListResponses.stream().collect(Collectors.groupingBy(SysRoleListResponse::getUserId));
            List<SysUserListResponse> list = records.stream().peek(x -> {
                x.setRoleList(listMap.get(x.getId()));
            }).collect(Collectors.toList());
            joinPage.setRecords(list);
        }
        return PageInfo.build(joinPage);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(SysUserAddRequest request) {
        if (CollectionUtils.isEmpty(request.getRoleId())) {
            throw new ServiceException("请配置角色");
        }
        // 校验登录名称是否重复
        String userName = request.getUserName();
        SysUser user = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUserName, userName));
        if (Objects.nonNull(user)) {
            throw new ServiceException("当前登录名已存在");
        }
        SysUser sysUser = new SysUser();
        BeanUtils.copyProperties(request, sysUser);
        sysUser.setId(IdUtils.getLongId());
        // 设置默认密码
        sysUser.setPassword(BCrypt.hashpw(Constant.DEFAULT_PASSWORD, BCrypt.gensalt()));
        sysUser.setRealName(request.getRealName());
        sysUser.setCreateTime(LocalDateTime.now());
        sysUser.setUpdateTime(sysUser.getCreateTime());
        sysUser.setAvatar(Constant.DEFAULT_AVATAR);
        sysUserMapper.insert(sysUser);
        // 新增用户关联角色
        request.getRoleId().forEach(roleId -> {
            saveUserRole(sysUser, roleId);
        });
        // 添加用户岗位关联关系
        this.saveUserPost(request, sysUser);
    }

    /**
     * 添加用户岗位关联
     *
     * @param request 请求参数
     * @param sysUser sysUser
     */
    private void saveUserPost(SysUserAddRequest request, SysUser sysUser) {
        // 删除关联关系
        sysUserPostMapper.deleteByUserId(sysUser.getId());
        SysUserPost userPost = new SysUserPost();
        userPost.setPostId(request.getPostId());
        userPost.setUserId(sysUser.getId());
        sysUserPostMapper.insert(userPost);
    }

    @Override
    public void delete(Long id) {
        SysUser sysUser = sysUserMapper.selectById(id);
        if (Objects.isNull(sysUser)) {
            throw new ServiceException("数据不存在");
        }
        sysUserMapper.deleteById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(SysUserUpdateRequest request) {
        SysUser sysUser = sysUserMapper.selectById(request.getId());
        if (Objects.isNull(sysUser)) {
            throw new ServiceException("数据不存在");
        }
        BeanUtils.copyProperties(request, sysUser);
        sysUserMapper.updateById(sysUser);
        //删除用户关联角色
        sysUserRoleMapper.deleteByUserId(sysUser.getId());
        if (request.getRoleId() != null) {
            for (Long roleId : request.getRoleId()) {
                saveUserRole(sysUser, roleId);
            }
        }
        // 删除关联关系 添加用户岗位关联表
        sysUserPostMapper.deleteByUserId(sysUser.getId());
        SysUserPost userPost = new SysUserPost();
        userPost.setPostId(request.getPostId());
        userPost.setUserId(sysUser.getId());
        sysUserPostMapper.insert(userPost);
    }

    private void saveUserRole(SysUser sysUser, Long roleId) {
        SysUserRole sysUserRole = new SysUserRole();
        sysUserRole.setId(IdUtils.getLongId());
        sysUserRole.setUserId(sysUser.getId());
        sysUserRole.setRoleId(roleId);
        sysUserRole.setCreateTime(LocalDateTime.now());
        sysUserRole.setUpdateTime(sysUserRole.getCreateTime());
        sysUserRoleMapper.insert(sysUserRole);
    }

    @Override
    public void dataUpdate(DataUpdateRequest updateDto) {
        // 校验登录名称是否重复
        String userName = updateDto.getUserName();
        SysUser user = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class)
                .eq(SysUser::getUserName, userName).ne(SysUser::getId,updateDto.getId()));
        if (Objects.nonNull(user)) {
            throw new ServiceException("当前登录名已存在");
        }
        SysUser sysUser = sysUserMapper.selectById(updateDto.getId());
        if (Objects.isNull(sysUser)) {
            throw new ServiceException("数据不存在");
        }
        BeanUtils.copyProperties(updateDto, sysUser);
        sysUserMapper.updateById(sysUser);
    }


    @Override
    public LoginResponse login(LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
        String cacheCode = redisCache.getCacheObject(Constant.CAPTCHA_NAME + Constant.COLON_SEPARATOR + loginRequest.getUuid());
        // 用户登录校验
        SysUser sysUser = this.checkLogin(loginRequest, cacheCode);
        LoginResponse loginResponse = new LoginResponse();
        SysUserDetailResponse detailResponse = new SysUserDetailResponse();
        BeanUtils.copyProperties(sysUser, detailResponse);
        loginResponse.setSysUser(detailResponse);
        // 获取菜单列表
        List<SysMenuResponse> menuVoList = sysMenuService.selectByUserId(sysUser.getId());
        loginResponse.setSysMenu(menuVoList);
        //生成令牌
        String token = tokenUtils.createToken(sysUser.getId(), sysUser.getUserName());
        redisCache.setCacheObject(Constant.USER_TOKEN_CACHE + sysUser.getId(), token, easyAdminConfig.getTokenExpireTime(), TimeUnit.MINUTES);
        loginResponse.setToken(token);
        // 异步线程记录登录日志
        HttpServletRequest servletRequest = Objects.requireNonNull(RequestUtils.getRequest());
        AsyncFactory.runAsync(() -> this.saveLoginRecord(servletRequest, loginRequest.getUserName(), sysUser.getId(), "登录成功"));
        return loginResponse;
    }

    /**
     * 保存登录记录
     *
     * @param username username
     * @param message  message
     */
    private void saveLoginRecord(HttpServletRequest request, String username, Long userId, String message) {
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        String ip = IpUtils.getNetIp();
        String address = IpUtils.getAddress();
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();
        SysLoginRecord loginRecord = new SysLoginRecord();
        loginRecord.setUserName(username);
        loginRecord.setIpaddr(ip);
        loginRecord.setLoginLocation(address);
        loginRecord.setBrowser(browser);
        loginRecord.setOs(os);
        loginRecord.setLoginTime(LocalDateTime.now());
        loginRecord.setMsg(message);
        loginRecord.setUserId(userId);
        sysLoginRecordMapper.insert(loginRecord);

    }


    @NotNull
    private SysUser checkLogin(LoginRequest loginRequest, String cacheCode) {
        HttpServletRequest request = Objects.requireNonNull(RequestUtils.getRequest());
        if (StringUtils.isEmpty(cacheCode)) {
            AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "验证码已过期"));
            throw new ServiceException("验证码已过期");
        }
        if (!cacheCode.equals(loginRequest.getCode())) {
            AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "验证码有误"));
            throw new ServiceException("验证码有误");
        }
        SysUser sysUser = this.selectByUserName(loginRequest.getUserName());
        if (Objects.isNull(sysUser)) {
            AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "用户名不存在"));
            throw new ServiceException("用户名不存在");
        }
        if (!BCrypt.checkpw(loginRequest.getPassword(), sysUser.getPassword())) {
            AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "密码错误"));
            throw new ServiceException("密码错误");
        }
        return sysUser;
    }

    @Override
    public UserInfoResponse getInfo(Long userId, HttpServletRequest request, HttpServletResponse response) {
        UserInfoResponse userInfoResponse = new UserInfoResponse();
        SysUser sysUser = sysUserMapper.selectById(userId);
        SysUserDetailResponse userDetail = new SysUserDetailResponse();
        BeanUtils.copyProperties(sysUser, userDetail);
        userInfoResponse.setSysUser(userDetail);
        List<SysMenuResponse> menuVoList = sysMenuService.selectByUserId(sysUser.getId());
        userInfoResponse.setSysMenu(menuVoList);
        return userInfoResponse;
    }

    @Override
    public void logout(HttpServletRequest request) {
        UserContextInfo userInfo = tokenUtils.getUserInfo(request);
        redisCache.deleteObject(Constant.USER_TOKEN_CACHE + userInfo.getId());
        AsyncFactory.runAsync(() -> this.saveLoginRecord(request, userInfo.getUserName(), userInfo.getId(), "退出登录"));
    }

    @Override
    public Integer getUnReadNum(Long userId) {
        return sysUserMessageStatsMapper.selectOne(Wrappers.lambdaQuery(SysUserMessageStats.class)
                .eq(SysUserMessageStats::getUserId, userId)).getUnRead();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePwd(PwdUpdateRequest updateDto) {
        UserContextInfo userContextInfo = ContextUserInfoThreadHolder.get();
        // 原密码对比
        SysUser sysUser = sysUserMapper.selectById(userContextInfo.getId());
        if (!BCrypt.checkpw(updateDto.getOldPwd(), sysUser.getPassword())) {
            throw new ServiceException("原密码错误");
        }
        if (!updateDto.getNewPwd().equals(updateDto.getConfirmPwd())) {
            throw new ServiceException("两次密码不一致");
        }
        sysUser.setPassword(BCrypt.hashpw(updateDto.getNewPwd(), BCrypt.gensalt()));
        sysUserMapper.updateById(sysUser);
    }

    @Override
    public String resetPwd(Long id) {
        SysUser sysUser = sysUserMapper.selectById(id);
        sysUser.setPassword(BCrypt.hashpw(Constant.DEFAULT_PASSWORD, BCrypt.gensalt()));
        sysUserMapper.updateById(sysUser);
        return Constant.DEFAULT_PASSWORD;
    }


}
